package com.pick.base.aspect;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.pick.base.util.IPUtil;
import com.pick.base.annotation.RateLimit;
import com.pick.base.config.RateLimitConfig;
import com.pick.base.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 限流切面
 * 实现基于注解的限流功能
 *
 * @author pick
 * @since 2024-01-01
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {

    private final RateLimitConfig rateLimitConfig;

    /**
     * 限流切点
     */
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        // 获取限流键
        String key = getRateLimitKey(joinPoint, rateLimit);
        
        // 检查限流
        long remaining = rateLimitConfig.checkRateLimit(key, rateLimit.window(), rateLimit.limit());
        
        if (remaining < 0) {
            // 超过限流，记录日志并抛出异常
            logRateLimitExceeded(joinPoint, rateLimit, key);
            throw new BusinessException("429", rateLimit.message());
        }
        
        // 记录限流信息
        logRateLimitInfo(joinPoint, rateLimit, key, remaining);
        
        // 继续执行
        return joinPoint.proceed();
    }

    /**
     * 获取限流键
     */
    private String getRateLimitKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
        String baseKey = getBaseKey(joinPoint, rateLimit);
        
        return switch (rateLimit.type()) {
            case IP -> {
                String ip = getClientIp();
                yield rateLimitConfig.getIpRateLimitKey(ip, baseKey);
            }
            case USER -> {
                String userId = getCurrentUserId();
                yield rateLimitConfig.getUserRateLimitKey(userId, baseKey);
            }
            case GLOBAL -> rateLimitConfig.getGlobalRateLimitKey(baseKey);
            case CUSTOM -> {
                if (StrUtil.isBlank(rateLimit.key())) {
                    throw new IllegalArgumentException("自定义限流类型必须指定key参数");
                }
                yield "rate_limit:custom:" + rateLimit.key();
            }
        };
    }

    /**
     * 获取基础键
     */
    private String getBaseKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
        if (StrUtil.isNotBlank(rateLimit.key())) {
            return rateLimit.key();
        }
        
        // 使用方法签名作为默认键
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return method.getDeclaringClass().getName() + "." + method.getName();
    }

    /**
     * 获取客户端IP
     */
    private String getClientIp() {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                return IPUtil.getClientIP(request);
            }
        } catch (Exception e) {
            log.warn("获取客户端IP失败: {}", e.getMessage());
        }
        return "unknown";
    }

    /**
     * 获取当前用户ID
     */
    private String getCurrentUserId() {
        try {
            if (StpUtil.isLogin()) {
                return StpUtil.getLoginIdAsString();
            }
        } catch (Exception e) {
            log.warn("获取当前用户ID失败: {}", e.getMessage());
        }
        return "anonymous";
    }

    /**
     * 记录限流超出日志
     */
    private void logRateLimitExceeded(ProceedingJoinPoint joinPoint, RateLimit rateLimit, String key) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        
        String ip = getClientIp();
        String userId = getCurrentUserId();
        
        log.warn("限流触发 - 方法: {}.{}, 类型: {}, 键: {}, IP: {}, 用户: {}, 窗口: {}秒, 限制: {}次",
                method.getDeclaringClass().getSimpleName(),
                method.getName(),
                rateLimit.type(),
                key,
                ip,
                userId,
                rateLimit.window(),
                rateLimit.limit());
    }

    /**
     * 记录限流信息
     */
    private void logRateLimitInfo(ProceedingJoinPoint joinPoint, RateLimit rateLimit, String key, long remaining) {
        if (log.isDebugEnabled()) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            
            log.debug("限流检查通过 - 方法: {}.{}, 键: {}, 剩余次数: {}",
                    method.getDeclaringClass().getSimpleName(),
                    method.getName(),
                    key,
                    remaining);
        }
    }
}